package webhook

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"gitverse.ru/synapse/kubelatte/pkg/kubeapi"
	"gitverse.ru/synapse/kubelatte/pkg/observability/logs"
	"gitverse.ru/synapse/kubelatte/pkg/sideeffect"
	"net/http"
	"os"
	"sync"

	admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"

	"github.com/pkg/errors"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"gitverse.ru/synapse/kubelatte/pkg/webhook/config"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer"
)

var (
	runtimeScheme = runtime.NewScheme()
	codecs        = serializer.NewCodecFactory(runtimeScheme)
	deserializer  = codecs.UniversalDeserializer()
)

type serverState int

const (
	ServerReady serverState = iota
	ServerConfigsError
	ServerCertificatesError
	ServerPending
	ServerNotReady
)

var ServerStateText = map[serverState]string{
	ServerReady:             "OK",
	ServerConfigsError:      "trigger or mutating config problem",
	ServerCertificatesError: "server certificates problem",
	ServerPending:           "server busy",
	ServerNotReady:          "server is not ready",
}

func init() {
	_ = admissionregistrationv1beta1.AddToScheme(runtimeScheme)
	_ = corev1.AddToScheme(runtimeScheme)
}

type Server struct {
	tlsServer           *http.Server
	httpServer          *http.Server
	config              *config.WebhookConfig
	certificateReloader util.CertificateReloader
	sideEffectLauncher  sideeffect.LauncherI
	state               serverState
	mux                 *sync.Mutex
}

// NewWebhookServer is a constructor for webhookServer
func NewWebhookServer(config *config.WebhookConfig, certificateReloader util.CertificateReloader, mux *sync.Mutex) *Server {
	kubeCli := kubeapi.GetClient()
	kbltCli, err := kubeapi.GetKBLTClient()
	if err != nil {
		logs.Error("KBLT client getting error:", err)
	}
	srv := &Server{
		sideEffectLauncher:  sideeffect.NewLauncher(sideeffect.NewExecutor(kbltCli, kubeCli)),
		config:              config,
		certificateReloader: certificateReloader,
		mux:                 mux,
	}
	return srv
}

func (whsvr *Server) Set(s serverState) {
	logs.Infof("Change server status to:%s ", ServerStateText[s])
	whsvr.state = s
}

func (whsvr *Server) Start() (chan bool, chan bool, error) {
	wconfig := whsvr.config

	var tlsConfig *tls.Config
	var startTLS bool

	if wconfig.TLSPort != 0 {
		crt := wconfig.CertFilePath
		key := wconfig.KeyFilePath
		if crt == "" {
			return nil, nil, errors.New("api=Start, reason=wconfig.CertFilePath, err=since the TLS port is set, you need to specify the file containing the x509 Certificate for HTTPS --cert-file-path or SERVER_KEY env")
		}
		if key == "" {
			return nil, nil, errors.New("api=Start, reason=wconfig.KeyFilePath, err=since the TLS port is set, you need to specify the file containing the x509 private key --key-file-path or SERVER_CERT env")
		}
		if _, err := os.OpenFile(crt, os.O_RDONLY, 0644); errors.Is(err, os.ErrNotExist) {
			return nil, nil, errors.Errorf("api=Start, reason=wconfig.CertFilePath, err=certificate file %s dont exist", crt)
		}
		if _, err := os.OpenFile(key, os.O_RDONLY, 0600); errors.Is(err, os.ErrNotExist) {
			return nil, nil, errors.Errorf("api=Start, reason=wconfig.KeyFilePath, err=key file %s dont exist", key)
		}

		startTLS = true
	}

	var doneListeningTLSChannel chan bool

	isTLS := whsvr.certificateReloader != nil

	if startTLS {
		if isTLS {
			err := whsvr.certificateReloader.Start()
			if err != nil {
				logs.Errorf("api=Start, reason=certReloader.Start, certFilePath=%q, keyFilePath=%q, caFilePath=%q, err=%v", wconfig.CertFilePath, wconfig.KeyFilePath, wconfig.CaFilePath, err)
				return nil, nil, errors.Errorf("api=Start, reason=certReloader.Start, certFilePath=%q, keyFilePath=%q, caFilePath=%q, err=%v", wconfig.CertFilePath, wconfig.KeyFilePath, wconfig.CaFilePath, err)
			}

			tlsConfig = &tls.Config{
				GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
					return whsvr.certificateReloader.GetCertificate()
				},
			}
			if !wconfig.AllowDeprecatedTLSConfig {
				tlsConfig.MinVersion = tls.VersionTLS12
				tlsConfig.CipherSuites = []uint16{
					tls.TLS_AES_256_GCM_SHA384,
					tls.TLS_AES_128_GCM_SHA256,
					tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
					tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
					tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
					tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
				}
			}

			if wconfig.CaFilePath != "" {
				caCert, err := os.ReadFile(wconfig.CaFilePath)
				if err != nil {
					logs.Errorf("api=Start, reason=ioutil.ReadFile, caFilePath=%q, err=%v", wconfig.CaFilePath, err)
					return nil, nil, errors.Errorf("api=Start, reason=ioutil.ReadFile, caFilePath=%q, err=%v", wconfig.CaFilePath, err)
				}
				caCertPool := x509.NewCertPool()
				caCertPool.AppendCertsFromPEM(caCert)
				tlsConfig.RootCAs = caCertPool
			}
		}
	}

	// Define routing for requests to the server.
	router := http.NewServeMux()
	logs.ChangeDebugLevelServerStart(fmt.Sprintf(":%v", wconfig.HTTPPort), router)
	if util.IsMutatorRole() || util.IsFullRole() {
		logs.Info("Run templatter in MUTATOR role")
		router.HandleFunc("/mutate", whsvr.mutateHandler)
	}
	if util.IsValidatorRole() || util.IsFullRole() {
		logs.Info("Run templatter in VALIDATOR role")
		router.HandleFunc("/validate", whsvr.validateHandler)
	}
	router.HandleFunc("/validate/tri", whsvr.immutableHandler)
	router.HandleFunc("/healthz", whsvr.healthz)
	router.HandleFunc("/liveness", whsvr.liveness)
	router.HandleFunc("/readyness", whsvr.readyness)
	router.HandleFunc("/status", whsvr.updateStatusHandler)

	if startTLS {
		// We create two servers: one that serves https requests, and another that serves http requests only.
		whsvr.tlsServer = &http.Server{
			Addr:      fmt.Sprintf(":%v", wconfig.TLSPort),
			TLSConfig: tlsConfig,
		}
		whsvr.tlsServer.Handler = router

		// Channel to indicate when the server stopped listening for some reason
		doneListeningTLSChannel := make(chan bool)

		// start webhook server in new routine
		go func() {
			if isTLS {
				if err := whsvr.tlsServer.ListenAndServeTLS("", ""); err != nil {
					logs.Errorf("api=Start, message=failed to listen and mutateHandler webhook server: %v", err)
				}
				doneListeningTLSChannel <- true
			}
		}()

	}

	whsvr.httpServer = &http.Server{
		Addr: fmt.Sprintf(":%v", wconfig.HTTPPort),
	}

	whsvr.httpServer.Handler = router

	// Channel to indicate when the server stopped listening for some reason
	doneListeningHTTPChannel := make(chan bool)

	logs.Info("api=Start, message=start http webhook server")

	go func() {
		if err := whsvr.httpServer.ListenAndServe(); err != nil {
			logs.Errorf("api=Start, reason='failed to listen and mutateHandler webhook HTTP server: %v'", err)
		}
		doneListeningHTTPChannel <- true
	}()

	return doneListeningTLSChannel, doneListeningHTTPChannel, nil
}

func (whsvr *Server) Stop() {
	logs.Infof("api=Stop, reason='shutting down webhook server gracefully...'")
	if whsvr.tlsServer != nil {
		whsvr.tlsServer.Shutdown(context.Background())
	}
	if whsvr.httpServer != nil {
		whsvr.httpServer.Shutdown(context.Background())
	}
	if whsvr.certificateReloader != nil {
		if whsvr.certificateReloader.IsRunning() {
			whsvr.certificateReloader.Stop()
		}
	}
}
